{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[参考1](https://blog.csdn.net/mfq1219/article/details/81945448)\n",
    "\n",
    "[参考2](https://blog.csdn.net/m0_37822019/article/details/79709617)\n",
    "\n",
    "ctypes是Python的一个外部库，提供和C语言兼容的数据类型，可以很方便地调用DLL中输出的C接口函数。\n",
    "\n",
    "## 1. 加载dll和取出函数\n",
    "\n",
    "```python\n",
    "from ctypes import *   \n",
    "dll = cdll.LoadLibrary(dllpath)   #dllpath是字符串\n",
    "dll = windll.LoadLibrary(dllpath)\n",
    "```\n",
    "\n",
    "上面两行使用哪一行,取决于导出函数的调用规范(cdecl或stdcall).也可以使用下面两行代替:\n",
    "\n",
    "```python\n",
    "dll = CDLL(dllpath)    #注意和上面大小写的区别\n",
    "dll = WinDLL(dllpath) \n",
    "```\n",
    "\n",
    "💊注意,这里使用的dll必须和python平台匹配,比如都是32位的或者都是64位的。因为本质上是一个exe加载一个dll，无法跨平台。\n",
    "\n",
    "加载dll后,可直接得到dll中的导出函数地址.\n",
    "\n",
    "```python\n",
    "func = dll.func_name  #func_name 是dll的导出函数\n",
    "```\n",
    "\n",
    "有时动态链接库导出c++函数时，并不是有效的Python标识符，例如 \"??2@YAPAXI@Z\" 。这种情况下，必须使用getattr 获取函数:\n",
    "\n",
    "```python\n",
    "func = getattr(cdll.msvcrt,\"??2@YAPAXI@Z\")\n",
    "```\n",
    "\n",
    "在Windows上，有些动态链接库导出函数不是用名字，而是用序号(ordinal)。这时需通过索引获取：\n",
    "\n",
    "```python\n",
    "func = cdll.kernel32[1]\n",
    "```\n",
    "\n",
    "## 加载C语言标准库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from ctypes import *\n",
    "from platform import system\n",
    "\n",
    "your = system()\n",
    "if your == 'Linux':\n",
    "    libc =  CDLL(\"libc.so.6\")\n",
    "elif your == 'Windows':\n",
    "    libc = cdll.msvcrt\n",
    "    \n",
    "    \n",
    "libc.printf(b\"Hello %s\\n\", b'Trump')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 函数参数和返回值\n",
    "\n",
    "### 类型对照表\n",
    "\n",
    "上面只是得到了函数地址,还无法进行函数调用.要进行正确的函数调用,需设置好参数和返回值类型.\n",
    "ctypes支持的原生数据类型如下:                                    \n",
    "\n",
    "| ctypes类型     | C 类型                                   | Python 类型                        |\n",
    "|:------------------ |:-------------------------------------- |:-----------------------------------|\n",
    "| c_char            | char                                       | 1-character string          |\n",
    "| c_wchar         | wchar_t                                  | 1-character unicode string |\n",
    "| c_byte            | char                                       | int/long                   |\n",
    "| c_ubyte          | unsigned char                        | int/long                   |\n",
    "| c_bool            | bool                                        | bool                       |\n",
    "| c_short           | short                                       | int/long                   |\n",
    "| c_ushort         | unsigned short                       | int/long                   |\n",
    "| c_int               | int                                           | int/long                   |\n",
    "| c_uint             | unsigned int                           | int/long                   |\n",
    "| c_long            | long                                        | int/long                   |\n",
    "| c_ulong          | unsigned long                        | int/long                   |\n",
    "| c_longlong     | __int64 or longlong                 | int/long                   |\n",
    "| c_ulonglong   | unsigned __int64 or unsigned long long | int/long                   |\n",
    "| c_float            | float                                        | float                                     |\n",
    "| c_double        | double                                     | float                                     |\n",
    "| c_longdouble | long double float                      | float                                     |\n",
    "| c_char_p        | char *                                       | string or None                     |\n",
    "| c_wchar_p     | wchar_t *                                 | unicode or None                  |\n",
    "| c_void_p        | void *                                        | int/long or None                  |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 设置参数类型和返回值类型\n",
    "\n",
    "- 设置函数的参数类型使用函数的`argtypes`属性,直接赋值为一个ctypes类型的列表或元组。\n",
    "- 设置函数的返回值类型使用函数的`restype`属性。下面是示例代码\n",
    "\n",
    "**python中，默认函数返回值是c_int型**，此类型可以不用显示设置函数的`restype`属性，如果是**参数类型是c_int型则需要设置。**\n",
    "\n",
    "```python\n",
    "fun.argtypes = (c_int, c_int, c_int, c_void_p) #设置函数参数类型为 int,int,int,void *\n",
    "fun.restype  = c_float #设置返回值类型为 float\n",
    "```\n",
    "\n",
    "**None、整数、字节串和（unicode）字符串是可以作为本地Python对象直接传递给函数调用的。**\n",
    "\n",
    "- None是作为C的**NULL指针**传递。\n",
    "- 字节串和字符串作为内存块指针传递(char* 或 wchar_t*)。\n",
    "- Python整数作为平台相关的C语言int类型传递，**其值会截断到C类型**。\n",
    "\n",
    "**除了整数、字节串和字符串以外Python类型的参数传递，必须使用ctypes类型做包装。**\n",
    "\n",
    "在调用函数时，如果使用了错误的参数数量和调用规范时，ctypes尝试保护调用。不幸的是该功能仅在Windows上有用。它通过检查函数返回栈来实现，所以尽管发生了错误，但是函数还是调用了。\n",
    "这很容易导致当前使用的整个Python环境崩溃，所以必须很小心的使用。\n",
    "\n",
    "---\n",
    "\n",
    "## 结构体\n",
    "\n",
    "除了上述的基本类型，ctypes还支持自定义的结构体和联合体，它们可以出现在函数的参数或返回值中。\n",
    "\n",
    "### 基本用法\n",
    "\n",
    "自定义的结构体和联合体必须**继承自ctypes的Structure和Union**，这两个类都在ctypes模块中定义。每一个子类必须定义`_fields_`属性，`_fields_`是一个**二维的tuples列表**，描述类的每个数据成员的字段名和字段类型，这里的字段类型必须是一个ctypes类型，如c_int，或者任何其他的继承ctypes的类型，如Structure, Union, Array, 指针等。\n",
    "\n",
    "例如有一个简单结构，包含两个整型x和y，可如下初始化一个结构："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 2\n"
     ]
    }
   ],
   "source": [
    "from ctypes import *  \n",
    "import types  \n",
    "    \n",
    "class Point(Structure):  \n",
    "    _fields_ = [('x', c_int),  \n",
    "                ('y', c_int)]  \n",
    "    \n",
    "p1 = Point(1, 2)  \n",
    "print(p1.x, p1.y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 嵌套结构体"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 2\n",
      "0 0\n"
     ]
    }
   ],
   "source": [
    "#可以创建复杂的结构体,嵌套了其它结构体。如下：\n",
    "class RECT(Structure):\n",
    "    _fields_ = [(\"upperleft\", Point),\n",
    "                (\"lowerright\", Point)]\n",
    "\n",
    "rc = RECT(p1)\n",
    "print(rc.upperleft.x, rc.upperleft.y)\n",
    "print(rc.lowerright.x, rc.lowerright.y) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "#嵌套结构体可以通过下面多种方法初始化：\n",
    "rc2 = RECT(Point(1,2), Point(3,4))\n",
    "rc3 = RECT((1,2), (3,4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 结构体指针\n",
    "\n",
    "如结构体用于链表操作，即包含指向结构体指针时，若直接定义："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'Test' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-1-f905e214da22>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtypes\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mclass\u001b[0m \u001b[0mTest\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mStructure\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      5\u001b[0m     _fields_ = [('x', c_int),  \n\u001b[0;32m      6\u001b[0m                 \u001b[1;33m(\u001b[0m\u001b[1;34m'y'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mc_char\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-1-f905e214da22>\u001b[0m in \u001b[0;36mTest\u001b[1;34m()\u001b[0m\n\u001b[0;32m      5\u001b[0m     _fields_ = [('x', c_int),  \n\u001b[0;32m      6\u001b[0m                 \u001b[1;33m(\u001b[0m\u001b[1;34m'y'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mc_char\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m                 ('next', Test)]   #这一行报错\n\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m: name 'Test' is not defined"
     ]
    }
   ],
   "source": [
    "from ctypes import *  \n",
    "import types  \n",
    "    \n",
    "class Test(Structure):  \n",
    "    _fields_ = [('x', c_int),  \n",
    "                ('y', c_char),  \n",
    "                ('next', Test)]   #这一行报错"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "则python会报错type未定义，如下定义则OK："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ctypes import *\n",
    "import types\n",
    "\n",
    "\n",
    "class Test(Structure):\n",
    "    pass\n",
    "\n",
    "\n",
    "Test._fields_ = [('x', c_int), ('y', c_char), ('next', POINTER(Test))]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 字节对齐和字节顺序\n",
    "\n",
    "默认情况下结构体和联合的对齐使用C编译器相同的方式。这可以通过类属性`_pack_` 来重载其行为。这必须设置一个正数指定字段的最大对齐。这个功能与MSVC中的 `#pragma pack(n)` 功能一样。\n",
    "\n",
    "`ctypes`中的结构体和联合使用本地字节序。想要用非本地字节序，可以使用 `BigEndianStructure` 、`LittleEndianStructure` 、 `BigEndianUnion` 、 `LittleEndianUnion` 基类。这些类无法包含指针字段。\n",
    "\n",
    "### 位域\n",
    "\n",
    "创建结构与联合体时，可以包含位域字段。**只有整型域才可以使用位字段**，位宽可以在`_fields_`元组的第三个选项中指定："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Int(Structure):\n",
    "    _fields_ = [(\"first_16\", c_int, 16),   #这个字段占16位\n",
    "                (\"second_16\",c_int, 16)]  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  数组\n",
    "\n",
    "数组就是序列，包含固定数量的相同类型的实例。**推荐的创建数组类型的方式是使用正数和乘号应用到类型:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n"
     ]
    }
   ],
   "source": [
    "from ctypes import *\n",
    "\n",
    "\n",
    "class POINT(Structure):\n",
    "    _fields_ = [(\"x\", c_int), \n",
    "                (\"y\", c_int)]\n",
    "\n",
    "\n",
    "TenPointsArrayType = POINT * 10  #创建一个数组类型,它是10个Point元素组成的数组。\n",
    "arr = TenPointsArrayType()  #创建一个数组类的对象。\n",
    "for pt in arr:\n",
    "    print(pt.x, pt.y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n"
     ]
    }
   ],
   "source": [
    "class MyStruct(Structure):\n",
    "    _fields_ = [(\"a\", c_int),\n",
    "                (\"b\", c_float),\n",
    "                (\"point_array\", POINT * 4)]  # 相当于C语言的:  POINT pts[4]\n",
    "\n",
    "\n",
    "print(len(MyStruct().point_array))  #输出  4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n",
      "6\n",
      "7\n",
      "8\n",
      "9\n",
      "10\n"
     ]
    }
   ],
   "source": [
    "TenIntegers = c_int * 10  # 定义一个int[10]的类型（类）\n",
    "\n",
    "ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  #显式初始化数组(类)\n",
    "for i in ii:\n",
    "    print(i)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 高维数组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "#高维数组,即数组的数组。\n",
    "type_int_array_10 = c_int * 10  #先定义一个数组类型\n",
    "type_int_array_10_10 = type_int_array_10 * 10  #定义数组的数组（即二维数组）\n",
    "my_array = type_int_array_10_10()  #创建二维数组对象\n",
    "my_array[1][2] = 3  #使用二维数组"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 指针和引用\n",
    "\n",
    "有时C函数需要一个指针指向的数据作为参数，或想向指针指向的内存块写输出数据，或者数据太大不适合传递，这时就需要使用指针或者引用。\n",
    "\n",
    "ctypes中使用`POINTER`和`pointer`表示指针，在使用`POINTER时`需要设置指向的**数据类型**，而`pointer`则直接从**变量**中得到一个特定类型的指针。\n",
    "\n",
    "ctypes使用 `byref()` 函数表示指针概念，该函数也是直接从变量得到指针指向的数据类型。\n",
    "\n",
    "对内存块的使用，实际上是区分输入/输出的。如果是该内存块是函数的输入（即函数内部是从内存块读数据），则使用指针，即`POINTER()` 或 `pointer()`。\n",
    "\n",
    "如果是该内存块是函数的输出（即函数内部是写数据到内存块），则需使用`create_string_buffer()` 函数。\n",
    "\n",
    "### 指针 POINTER、pointer\n",
    "\n",
    "例如创建一个类似于C语言的`int *`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n",
      "c_long(4)\n"
     ]
    }
   ],
   "source": [
    "type_p_int = POINTER(c_int)  #创建指针类型,它指向整数\n",
    "v = c_int(4)  #定义一个整数,值为4.\n",
    "p_int = type_p_int(v)  #给一个指针变量(p_int)赋值(为变量v的地址).\n",
    "\n",
    "print(p_int[0])\n",
    "print(p_int.contents)  #指针实例有一个contents属性，返回这个指针所指向的对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4 3\n",
      "c_long(4)\n"
     ]
    }
   ],
   "source": [
    "type_p_int = POINTER(c_int)  #创建指针类型,它指向整数\n",
    "v = c_int(4)  #定义一个整数,值为4.\n",
    "p_int = type_p_int(v)  #给一个指针变量(p_int)赋值(为变量v的地址).\n",
    "p_int[2] = 3\n",
    "\n",
    "print(p_int[0], p_int[2])\n",
    "print(p_int.contents)  #指针实例有一个contents属性，返回这个指针所指向的对象。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面这段代码在C语言里相当于：\n",
    "```c\n",
    "typedef int * type_p_int;\n",
    "int v = 4;\n",
    "type_p_int p = &v;\n",
    "printf(\"%d\", p[0]);\n",
    "printf(\"%d\", *p);\n",
    "```\n",
    "\n",
    "也可以不经过声明指针类型这一步，直接从变量得到指针，如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4 3\n",
      "c_long(4)\n"
     ]
    }
   ],
   "source": [
    "v = c_int(4)        #定义一个整数,值为4.\n",
    "p_int = pointer(v)  #直接得到v的指针，不需创建指针类型（省去类型声明）。\n",
    "\n",
    "p_int[2] = 3\n",
    "print(p_int[0], p_int[2])\n",
    "print(p_int.contents)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意：对指针类型 `c_char_p`，`c_wchar_p`，`c_void_p` 的赋值将会改变其指向的内存区域地址，而不是改变内存块的值**（因为Python字符串是只读的）**。\n",
    "\n",
    "### 引用：byref()\n",
    "\n",
    "`ctypes`使用 `byref()` 函数**传递参数引用**。通常使用 `byref()`的地方同样也可用指针函数`pointer()`，但`pointer()`作为参数通常会额外创建一个指针对象，如果并不需要再次使用该指针对象的话，使用 `byref()` 会更快。\n",
    "\n",
    "## 内存块\n",
    "\n",
    "各种指针类型（`c_char_p`，`c_wchar_p`，`c_void_p`）指向的内存块实际上都是只读的。如果某个函数需要一个输入内存块保存输出值，不能传递这些指针。我们需要一个可写的内存块，使用`create_string_buffer()` 函数创建。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3 b'\\x00\\x00\\x00'\n"
     ]
    }
   ],
   "source": [
    "from ctypes import *\n",
    "\n",
    "p = create_string_buffer(3)  #创建3字节长的buf，且初始化为0\n",
    "print(sizeof(p), repr(p.raw))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "b'Hello\\x00'\n",
      "b'Hello'\n"
     ]
    }
   ],
   "source": [
    "p = create_string_buffer(b\"Hello\")  #创建一个字符串(包括结尾的0)的buf\n",
    "print(sizeof(p))\n",
    "print(p.raw)\n",
    "print(p.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 b'Hello\\x00\\x00\\x00\\x00\\x00'\n"
     ]
    }
   ],
   "source": [
    "p = create_string_buffer(b\"Hello\", 10)\n",
    "print(sizeof(p), p.raw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 b'Hi\\x00lo\\x00\\x00\\x00\\x00\\x00'\n"
     ]
    }
   ],
   "source": [
    "p.value = b\"Hi\"  #修改buf内容（这是可变buf）\n",
    "print(sizeof(p), p.raw) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "想要创建包含unicode字符(对应C类型`wchar_t`)的可变内存块，使用`create_unicode_buffer()` 函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6 \n"
     ]
    }
   ],
   "source": [
    "pw = create_unicode_buffer(3)  #创建一个unicode使用的buf，且初始化为0\n",
    "print(sizeof(pw), pw.value)  #注意，这里将输出6。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 类型转换\n",
    "\n",
    "通常情况下，ctypes会做严格的类型检查。这意味着，如果形参有一个`POINTER(c_int)`指针指向一个函数或者结构体的成员域类型，那么实参只能接受相同类型的实例。\n",
    "但这个规则也有例外。比如，你可以传递兼容的数组类型来代替指针类型。例如对于`POINTER(c_int)`指针类型来说，可以使用`c_int`数组来代替。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "class Bar(Structure):\n",
    "    _fields_ = [(\"count\", c_int), \n",
    "                (\"values\", POINTER(c_int))]\n",
    "\n",
    "\n",
    "bar = Bar()\n",
    "bar.values = (c_int * 3)(1, 2, 3)  #数组和指针的转化\n",
    "bar.count = 3\n",
    "for i in range(bar.count):\n",
    "    print(bar.values[i])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "NULL pointer access",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-57-7c9c9f93f364>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mbar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcount\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m     \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mbar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m: NULL pointer access"
     ]
    }
   ],
   "source": [
    "bar.values = None  #设置指针为NULL\n",
    "\n",
    "for i in range(bar.count):\n",
    "    print(bar.values[i])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果一个函数参数显式声明为某种指针类型（例如`POINT(c_int)` 类型），则传递该指针指向的对象类型也是可以的（例如这里可以传递`c_int`），`ctypes`会自动加上`byref()`函数进行类型转换。\n",
    "\n",
    "在C语言中，你可以通过**强制类型转换**的方法来转换不兼容的类型。ctypes也提供了一个转换函数`cast()` 让你可以使用相同的方式进行类型转换。\n",
    "`cast()`函数可以将一个ctypes指针（或数组）的实例转换成另外一个不同的指针类型（或数组）。`cast()`函数需要两个参数，第一个是转换前的**指针实例**，\n",
    "第二个是目标**指针类型。它返回第二个参数类型的实例，并且这个实例与第一个参数共用同一块内存。**\n",
    "\n",
    "上面定义的`Bar`结构体中，它的`value`域可以支持`POINTER(c_int)`指针或者`c_int`数组，但不支持其他类型，如果需要其它类型，则可使用类型转换。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "incompatible types, c_byte_Array_4 instance instead of LP_c_long instance",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-58-aa55b169fba3>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mbar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalues\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mc_byte\u001b[0m \u001b[1;33m*\u001b[0m \u001b[1;36m4\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m  \u001b[1;31m#报错，类型不对。需要 int * 或 int 数组。这里是byte数组。\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance"
     ]
    }
   ],
   "source": [
    "bar.values = (c_byte * 4)()  #报错，类型不对。需要 int * 或 int 数组。这里是byte数组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "bar.values = cast((c_byte * 4)(), POINTER(c_int))  #正确。强制转换,把byte数组转化为int*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n"
     ]
    }
   ],
   "source": [
    "print(bar.values[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 回调函数\n",
    "\n",
    "types允许从python回调中创建c回调函数指针。这个常常被称为回调函数。\n",
    "\n",
    "首先，你必须为回调函数创建一个类，这个类知道调用协议，函数返回值类型，函数接受的参数个数及类型。\n",
    "\n",
    "`CFUNCTYPE`工厂函数使用普通`cdecl`调用协议来为回调函数**创建类型**。并且，在Windows平台，`WINFUNCTYPE`工厂函数使用`stdcall`调用协议来为回调函数创建类型。\n",
    "这两个工厂函数在调用时，**参数表都是使用返回值作为第一个参数，而将回调函数所需要的参数作为剩下的参数。**\n",
    "\n",
    "在这里我将使用一个c标准库里的快排函数作为演示例子，快排是一个借助回调函数进行排序的函数。快排将会用到下面的整型数组："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "py_cmp_func 1 5\n",
      "py_cmp_func 7 5\n",
      "py_cmp_func 33 7\n",
      "py_cmp_func 99 33\n",
      "py_cmp_func 1 5\n",
      "py_cmp_func 7 5\n",
      "py_cmp_func 33 7\n",
      "py_cmp_func 1 5\n",
      "py_cmp_func 7 5\n",
      "py_cmp_func 1 5\n",
      "1 5 7 33 99 "
     ]
    }
   ],
   "source": [
    "CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))  #定义回调函数类型\n",
    "\n",
    "\n",
    "def py_cmp_func(a, b):  #实现回调函数功能\n",
    "    print(\"py_cmp_func\", a[0], b[0])\n",
    "    return a[0] - b[0]\n",
    "\n",
    "\n",
    "cmp_func = CMPFUNC(py_cmp_func)  #回调函数对象\n",
    "\n",
    "IntArray5 = c_int * 5\n",
    "ia = IntArray5(5, 1, 7, 33, 99)\n",
    "qsort = libc.qsort  #排序函数地址\n",
    "qsort.restype = None  #排序函数返回值\n",
    "\n",
    "qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func))  #调用排序函数,传入一个回调函数对象.\n",
    "\n",
    "for i in ia:\n",
    "    print(i, end=\" \")\n",
    "\n",
    "#输出结果为： 1 5 7 33 99"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "💊**回调函数的重要提示：**\n",
    "\n",
    "确保你在C代码的使用生命周期里保持引用CFUNCTYPE对象。ctypes并不会帮你做这样的事情，如果你没有做保证，它们就会被垃圾回收，然后当你调用这个回调函数时将会导致程序崩溃。\n",
    "\n",
    "## 8.windows类型\n",
    "\n",
    "Windows API有一些特殊之处，Windows API函数不使用标准C的调用约定（前面已经提到过）。\n",
    "\n",
    "因此需注意两点：\n",
    "\n",
    "- LoadLibrary时不能够使用`cdll.LoadLibrary`而使用`windll.LoadLibrary`。\n",
    "- 在声明回调函数指针类型的时候，不能用`CFUNCTYPE`而是用`WINFUNCTYPE`。\n",
    "\n",
    "😜Windows API有很多内建类型，ctypes内部都已经定义好了，在子模块`wintypes`下，可以直接使用。\n",
    "\n",
    "代码举例如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ctypes import *\n",
    "from ctypes import wintypes\n",
    "\n",
    "# HWND 等类型已定义好了，可直接使用.\n",
    "WNDENUMPROC = WINFUNCTYPE(\n",
    "    wintypes.BOOL,  #定义回调函数类型\n",
    "    wintypes.HWND,\n",
    "    wintypes.LPARAM)\n",
    "\n",
    "\n",
    "def EnumWindowsProc(hwnd, lParam):  #实现回调函数功能\n",
    "    length = user32.GetWindowTextLengthW(hwnd) + 1\n",
    "    buffer = create_unicode_buffer(length)\n",
    "    user32.GetWindowTextW(hwnd, buffer, length)\n",
    "    print(buffer.value)\n",
    "    return True\n",
    "\n",
    "\n",
    "user32 = windll.LoadLibrary('user32.dll')  #加载dll\n",
    "user32.EnumWindows(WNDENUMPROC(EnumWindowsProc), 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 结构体指针\n",
    "\n",
    "返回结构体指针的函数. c代码如下:\n",
    "```c\n",
    "include <stdlib.h>\n",
    "typedef struct{\n",
    "  int a;\n",
    "  int b;\n",
    "}mystruct;\n",
    "\n",
    "mystruct * create(){\n",
    "  mystruct * s = (mystruct *)calloc(1, sizeof(mystruct));\n",
    "  s->a = 100;\n",
    "  s->b = 200;\n",
    "  return s;\n",
    "}\n",
    "\n",
    "void destroy(mystruct * s){\n",
    "  free(s);\n",
    "}\n",
    "```\n",
    "python代码如下：\n",
    "\n",
    "```py\n",
    "from ctypes import *\n",
    "\n",
    "\n",
    "class mystruct(Structure):\n",
    "    _fields_ = [('a', c_int), \n",
    "                ('b', c_int)]\n",
    "\n",
    "\n",
    "dll = cdll.LoadLibrary(dllpath)\n",
    "dll.create.restype = POINTER(mystruct)  #设置返回值类型为结构体指针\n",
    "p = dll.create()  #调用函数\n",
    "print(p.contents.a, p.contents.b)  #输出 100 200\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果返回的是结构体数组指针，同样也是将函数返回类型设置为结构体指针，在接收到值之后，使用时加上下标即可,注意是结构体数组指针，不是结构体指针数组\n",
    "python代码如下：\n",
    "\n",
    "```python\n",
    "from ctypes import *\n",
    "\n",
    "\n",
    "class mystruct(Structure):\n",
    "    _fields_ = [('a', c_int), ('b', c_int)]\n",
    "\n",
    "\n",
    "dll = cdll.LoadLibrary(dllpath)\n",
    "dll.create.restype = POINTER(mystruct)  #设置返回值类型为结构体指针\n",
    "p = dll.create()  #调用函数\n",
    "print(p[i].a, p[i].b)  #注意是结构体数组指针，不是结构体指针数组，不然应该还需要\n",
    "##如果是向函数内部传入一个结构体数组指针，而函数需要改变此结构体的内存，则可以作如下处理：\n",
    "```\n",
    "\n",
    "返回结构体指针参数的函数. c代码如下:\n",
    "    \n",
    "```c\n",
    "include <stdlib.h>\n",
    "typedef struct{\n",
    "  int a;\n",
    "  int b;\n",
    "}mystruct;\n",
    "\n",
    " int change(mystruct*stru,int*num){\n",
    " \n",
    " for(i＝０;i < 3;i++)\n",
    "  {\n",
    "   stru[i].a = i;\n",
    "   stru[i].b = i+1;\n",
    "  }\n",
    "  \n",
    "  int n_num = 2;\n",
    "  num = &n_num; \n",
    "  return 0;\n",
    "}\n",
    "```\n",
    "python代码如下：\n",
    "\n",
    "```python\n",
    "from ctypes import *  \n",
    "class mystruct(Structure):\n",
    "    _fields_ = [('a', c_int),('b', c_int)]\n",
    "dll = cdll.LoadLibrary(dllpath) \n",
    "stru_info= create_string_buffer(sizeof(mystruct) * NUM)\n",
    "p_rec = POINTER(mystruct)(stru_info)\n",
    "info_num = c_int()\n",
    "ret = dll.create(p_rec, byref(info_num)) #调用函数\n",
    "print(p_rec[i],p_rec[i].b)\n",
    "```\n",
    "\n",
    "同理，如果创建int类型的数组内存，则可以用\n",
    "\n",
    "```python\n",
    "int_buffer = create_string_buffer(sizeof(c_int) * NUM)\n",
    "p_int_buffer = POINTER(c_int)(int_buffer)\n",
    "```\n",
    "\n",
    "传递参数时直接使用`p_int_buffer`\n",
    "使用时直接`p_int_buffer[i]`取值即可\n",
    "\n",
    "如果是向函数内部传入一个结构体数组指针，而函数不需要改变此结构体的内存，则可以作如下处理：\n",
    "c代码如下:\n",
    "\n",
    "```c\n",
    "include <stdlib.h>\n",
    "typedef struct{\n",
    "  int a;\n",
    "  int b;\n",
    "}mystruct;\n",
    "\n",
    " int change(mystruct*stru,int num){\n",
    " \n",
    " for(i＝０;i < num;i++)\n",
    "  {\n",
    "   cout<<stru[i].a<<endl;\n",
    "   cout<<stru[i].b<<endl;\n",
    "  }\n",
    "  \n",
    "  return 0;\n",
    "}\n",
    "```\n",
    "\n",
    "python代码如下：\n",
    "\n",
    "```python\n",
    "from ctypes import *  \n",
    "class mystruct(Structure):\n",
    "    _fields_ = [('a', c_int),('b', c_int)]\n",
    "dll = cdll.LoadLibrary(dllpath) \n",
    "info_num = 3\n",
    "stru_info_list = []#添加一些结构体，此处略去\n",
    "stru_info_p = (mystruct*info_num)(*stru_info_list)\n",
    "num = c_int(info_num)\n",
    "ret = dll.create(byref(strstru_info_p), num) #调用函数\n",
    "print(p_rec[i],p_rec[i].b)\n",
    "```\n",
    "\n",
    "同理，如果只是传递一个数组指针，不需要改变内容的话，假设传第一个int数组：\n",
    "\n",
    "```python\n",
    "int_list = []\n",
    "num = len(int_list)\n",
    "p_int_list = (c_int*num)(*int_list)\n",
    "```\n",
    "\n",
    "### 解析\n",
    "\n",
    "上述例子中，如果创建int类型的数组内存\n",
    "假设c函数是\n",
    "```c\n",
    "int fun（int*buffer_list）#传递的是int数组的指针\n",
    "int_buffer = create_string_buffer(sizeof(c_int) * NUM)\n",
    "```\n",
    "\n",
    "如果直接传递的参数为`byref（int_buffer）`\n",
    "即：\n",
    "```python\n",
    "ret = dll.fun（byref（int_buffer））\n",
    "```\n",
    "\n",
    "那么对于`int_buffer`的使用就需要解析\n",
    "首先需要取`int_buffer`得二进制内容，即`int_buffer.raw`，然后解析\n",
    "\n",
    "```python\n",
    "buffer = int_buffer.raw\n",
    "buffer_int = (struct.unpack('i', buffer[i * 4 : (i + 1) * 4]))[0]\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "183px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
